package com.derekandbritt.booST.endpoints;

import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.HashMap;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;

import android.util.Log;

import com.derekandbritt.booST.exceptions.ConnectionException;
import com.derekandbritt.booST.exceptions.DataFormatException;
import com.google.gson.Gson;

public abstract class JSONEndpoint {
	
    private static final boolean DEBUG = true;
    private static final String LOG_TAG = "JSONEndpoint"; 
    
	protected static final Gson gson = new Gson();
	
	private String hostName;
	private String contextRoot;
	
	public String getHostName() {
		return hostName;
	}

	public void setHostName(String hostName) {
		this.hostName = hostName;
	}

	public String getContextRoot() {
		return contextRoot;
	}

	public void setContextRoot(String contextRoot) {
		this.contextRoot = contextRoot;
	}

	/* Can be overridden by subclasses */
	protected Gson getGson() {
	    return gson;
	}
	
	public abstract String getDefaultHostName();
	public abstract String getDefaultContextRoot();
	public abstract String getEndpointName();
	
	private String getServerAddress() {
		if(hostName == null && contextRoot == null)
			return getDefaultHostName() + "/" + getDefaultContextRoot() + "/";
		else if(hostName == null)
			return getDefaultHostName() + "/" + contextRoot + "/";
		else if(contextRoot == null)
			return hostName + "/" + getDefaultContextRoot() + "/";
		else
			return hostName + "/" + contextRoot + "/";
	}
	
	public boolean testServer(String hostName) {
		
		String currentHostName = hostName;
		boolean result = false;
		
		try {
			this.hostName = hostName;
			String jsonResult = getData("ServiceTest", new HashMap<String, String>(), true);
			result = getGson().fromJson(jsonResult, boolean.class);
		} catch (ConnectionException e) {
		} catch (DataFormatException e) {
		} finally {
			this.hostName = currentHostName;
		}
		
		return result;
	}
	
	protected void preGetRequest(HashMap<String, String> map) { /* intended to be overridden */ }
	
	private String computeServletPath(String servletPath, HashMap<String, String> map) throws DataFormatException {
	    
	    try {
    	    StringBuffer buffer = new StringBuffer(); 
            for(String key : map.keySet()) {        
                buffer.append(URLEncoder.encode(key, "UTF-8") + "=" + URLEncoder.encode(map.get(key), "UTF-8") + "&");  
            }
            
            buffer.insert(0, "?");
            buffer.deleteCharAt(buffer.length() - 1);
            
            return servletPath + buffer.toString();
	    }
	    catch(UnsupportedEncodingException e) {
	        throw new DataFormatException("The request parameters were malformed.", e);
	    }
	}
	
	protected String getData(String servletPath, HashMap<String, String> map, boolean requiresResponse)  throws ConnectionException, DataFormatException {
        
	    preGetRequest(map);
	    
	    String result = null;
	    long startTime = System.currentTimeMillis();
	    
	    String serverAddress = "http://" + getServerAddress() + computeServletPath(servletPath, map);
	    
		try {			
			// Send data
	        HttpClient httpClient = new DefaultHttpClient();
	        HttpContext localContext = new BasicHttpContext();
	        HttpGet httpGet = new HttpGet(serverAddress);
	        HttpResponse response = httpClient.execute(httpGet, localContext);
	        
	        if(requiresResponse) {
		        InputStream is = response.getEntity().getContent();
				ObjectInputStream ois = new ObjectInputStream(is);
				String jsonResult = (String) ois.readObject();
				ois.close();
				result = jsonResult;
	        } else {
	        	result = response.getStatusLine().getStatusCode() + "";
	        }

		} catch(IOException e) {
			throw new ConnectionException("Unable to access the " + getEndpointName() + " server.", e);
		} catch (ClassNotFoundException e) {
			throw new DataFormatException("The request for data was malformed.", e);
		}
		
		if(DEBUG) {
		    long totalTime = System.currentTimeMillis() - startTime;
		    Log.d(LOG_TAG, "GET Request [" + totalTime + "] " + serverAddress);
		}
		
		return result;
	}
	
	protected void sendData(String servletPath, HashMap<String, String> map, String json) throws ConnectionException, DataFormatException {
	    sendData(computeServletPath(servletPath, map), json, false);
	}
	
	@Deprecated
	protected void sendData(String servletPath, HashMap<String, String> map, String json, boolean useObjectOS) throws ConnectionException, DataFormatException {
        sendData(computeServletPath(servletPath, map), json, useObjectOS);
    }
	
	protected void sendData(String servletPath, String json) throws ConnectionException, DataFormatException {
	    sendData(servletPath, json, false);
	}
	
	protected void sendData(String servletPath, String json, boolean useObjectOS) throws ConnectionException, DataFormatException {
	    
	    long startTime = System.currentTimeMillis();
	        
        String serverAddress = "http://" + getServerAddress() + servletPath;
	    
		try {
		
		// Send data
        URL url = new URL(serverAddress);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setDoOutput(true);
        conn.setDoInput(false);
        conn.setRequestMethod("POST");
        
        if(useObjectOS) {
            ObjectOutputStream oos = new ObjectOutputStream(conn.getOutputStream());
            oos.writeObject(json);
            oos.close();
        } else {
            //conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
            OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream());
            writer.write("content=" + json);
            writer.close();
        }
		
		int responseCode = conn.getResponseCode();
		
		if(responseCode == 404)
			throw new ConnectionException("Unable to access the " + getEndpointName() + " server.");
		
		} catch(IOException e) {
			throw new ConnectionException("Unable to access the " + getEndpointName() + " server.", e);
		}
		
		if(DEBUG) {
            long totalTime = System.currentTimeMillis() - startTime;
            Log.d(LOG_TAG, "POST Request [" + totalTime + "] " + serverAddress);
        }
	}
}
